<template>
  <div class="bpmn-containers" :class="{ 'view-mode': isView }">
    <el-container style="height: 100%">
      <el-container>
        <el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;padding: 10px 0 10px 10px">
          <el-button :size="formSize" icon="el-icon-circle-plus" @click="newDiagram">新建</el-button>
          <el-button :size="formSize" icon="el-icon-view" @click="saveXml">查看xml</el-button>
          <el-upload
            ref="upload"
            :limit="1"
            action=""
            :show-file-list="false"
            :before-upload="openBpmn"
            accept=".bpmn, .xml"
            style="display: inline-block;padding: 0 10px"
          >
            <el-button :size="formSize">点击上传</el-button>
          </el-upload>
          <el-button :size="formSize" icon="el-icon-zoom-in" @click="zoomViewport(true)">放大</el-button>
          <el-button :size="formSize" icon="el-icon-zoom-out" @click="zoomViewport(false)">缩小</el-button>
          <el-button :size="formSize" icon="el-icon-back" @click="bpmnModeler.get('commandStack').undo()">后退</el-button>
          <el-button :size="formSize" icon="el-icon-right" @click="bpmnModeler.get('commandStack').redo()">前进</el-button>
          <el-button :size="formSize" icon="el-icon-rank" @click="fitViewport">自适应屏幕</el-button>
          <div ref="canvas" class="canvas" />
        </el-header>
        <el-main class="bpmn-main">
          <div class="bpmn-hint">
            <div class="border" />&nbsp;已经过的节点&nbsp;&nbsp;
            <div class="border" />&nbsp;当前节点&nbsp;&nbsp;
            <div class="border" />&nbsp;未经过的节点&nbsp;&nbsp;
          </div>
          <div id="canvas" ref="canvas" class="canvas" />
        </el-main>
      </el-container>
      <el-aside style="width: 400px;height: 100%;">
        <panel
          v-show="!isView"
          v-if="bpmnModeler"
          class="panel"
          :bpmn-modeler="bpmnModeler"
          :btn-options="btnOptions"
          :form-size="formSize"
          :form-property="formProperty"
          :form-key="formKey"
        />
      </el-aside>
    </el-container>
  </div>
</template>

<script>
import CustomModeler from './utils/customModeler'
import flowableModule from '@/mock/flowable.json'
import getInitStr from './utils/init'
import Panel from './components/PropertyPanel'
import { btnOpt } from './utils/options'
import { mapActions } from 'vuex'
import lintModule from 'bpmn-js-bpmnlint'
import * as bpmnlintConfig from '../packed-config'
import fileDrop from 'file-drops'
import { append as svgAppend, create as svgCreate, remove as svgRemove } from 'tiny-svg'

export default {
  name: 'JinBpmn',
  components: { Panel },
  props: {
    xml: { // 加载流程
      type: String,
      default: ''
    },
    // 是否显示编辑菜单，默认显示，false显示，true不显示
    isView: {
      type: Boolean,
      default: false
    },
    btnOptions: {
      type: Array,
      default: () => btnOpt
    },
    formSize: {
      type: String,
      default: 'mini'
    },
    // [{id: "表单id", name: "表单名称", "property": [表单属性] }]
    // 表单属性：{id:"字段属性id", name: "字段名称", "readable": "字段可读默认值", "writable":"字段可写默认值"}
    formProperty: {
      type: Array,
      default: () => []
    },
    // 流程表单处理信息，用于流程显示明细信息
    formDetail: {
      type: Array,
      default: () => []
    },
    // 值是formProperty的id属性，用于打开绑定表单
    formKey: {
      type: String,
      default: ''
    },
    // 当前节点
    todoList: {
      type: Array,
      default: () => []
    },
    completeList: {
      type: Array,
      default: () => []
    },
    // 移动到节点上时，是否显示dialog
    showDialog: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      // bpmn建模器
      bpmnModeler: null,
      container: null,
      canvas: null,
      zoom: null,
      showPanel: true,
      taskList: [],
      dialogText: {
        x: -65, y: 80,
        value: []
      }
    }
  },
  mounted() {
    this.init()
    this.changeFormKey(this.formKey)
  },
  methods: {
    ...mapActions('bpmn', ['changeFormKey']),
    init() {
      // 建模
      this.createModeling()
      // 设置验证
      this.setValidator()
      // 初始化流程图
      this.createNewDiagram(this.xml ? this.xml : getInitStr())
      // 绑定事件
      this.bindEvent()
    },
    createModeling() {
      const that = this
      this.bpmnModeler = new CustomModeler({
        container: this.$refs.canvas,
        linting: !this.isView ? {
          bpmnlint: bpmnlintConfig,
          active: that.getUrlParam('linting')
        } : {},
        additionalModules: [
          !this.isView ? lintModule : '',
          this.isView ? {
            paletteProvider: ['value', ''], // 禁用/清空左侧工具栏
            labelEditingProvider: ['value', ''], // 禁用节点编辑
            contextPadProvider: ['value', ''], // 禁用图形菜单
            bendpoints: ['value', {}], // 禁用连线拖动
            zoomScroll: ['value', ''], // 禁用滚动
            moveCanvas: ['value', ''], // 禁用拖动整个流程图
            move: ['value', '']// 禁用单个图形拖动
          } : ''
        ],
        moddleExtensions: {
          flowable: flowableModule
        }
      })
    },
    newDiagram() {
      this.createNewDiagram(getInitStr())
    },
    async createNewDiagram(data) {
      // 将字符串转换成图显示出来
      data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
        return str.replace(/</g, '&lt;')
      })
      const info = await this.bpmnModeler.importXML(data)
      if (info.warnings && info.warnings.length > 0) {
        console.error('出错', info)
      } else {
        // 适应屏幕
        this.fitViewport()
        // 修改样式
        this.fillColor()
      }
    },
    setValidator() {
      const that = this
      this.bpmnModeler.on('linting.toggle', function(event) {
        const active = event.active
        that.setUrlParam('linting', active)
      })

      const dndHandler = fileDrop('Drop BPMN Diagram here.', function(files) {
        this.bpmnModeler.importXML(files[0].contents)
      })
      document.querySelector('body').addEventListener('dragover', dndHandler)
    },
    getUrlParam(name) {
      const url = new URL(window.location.href)
      return url.searchParams.has(name)
    },
    setUrlParam(name, value) {
      const url = new URL(window.location.href)
      if (value) {
        url.searchParams.set(name, 1)
      } else {
        url.searchParams.delete(name)
      }
      window.history.replaceState({}, null, url.href)
    },
    async saveXml() {
      try {
        const { xml } = await this.bpmnModeler.saveXML({ format: true })
        console.log(xml)
        // console.log(this.getProcessElement())
      } catch (err) {
        console.error(err)
      }
    },
    getProcessElement() {
      const rootElements = this.bpmnModeler.getDefinitions().rootElements
      for (let i = 0; i < rootElements.length; i++) {
        if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
      }
    },
    zoomViewport(zoomIn = true) {
      this.zoom = this.bpmnModeler.get('canvas').zoom()
      this.zoom += (zoomIn ? 0.1 : -0.1)
      this.bpmnModeler.get('canvas').zoom(this.zoom)
    },
    fitViewport() {
      this.zoom = this.bpmnModeler.get('canvas').zoom('fit-viewport')
      const oBox = document.querySelector('.bpmn-containers .viewport').getBBox()
      const currentViewBox = this.bpmnModeler.get('canvas').viewbox()
      const elementMid = {
        x: oBox.x + oBox.width / 2 - 65,
        y: oBox.y + oBox.height / 2
      }
      this.bpmnModeler.get('canvas').viewbox({
        x: elementMid.x - currentViewBox.width / 2,
        y: elementMid.y - currentViewBox.height / 2,
        width: currentViewBox.width,
        height: currentViewBox.height
      })
      this.zoom = oBox.width / currentViewBox.width * 1.8
    },
    fillColor() {
      const canvas = this.bpmnModeler.get('canvas')
      this.bpmnModeler._definitions.rootElements[0].flowElements.forEach(n => {
        if (n.$type === 'bpmn:SequenceFlow') {
          // canvas.addMarker(n.id, 'jin-bpmn')
        }
        if (n.$type === 'bpmn:UserTask') {
          if (this.todoList.includes(n.id)) {
            canvas.addMarker(n.id, 'jin-bpmn-todo')
          }
          if (this.completeList.includes(n.id)) {
            canvas.addMarker(n.id, 'jin-bpmn-complete')
          }
        }
      })
    },
    bindEvent() {
      this.bpmnModeler.on('element.hover', e => {
        const elementRegistry = this.bpmnModeler.get('elementRegistry')
        const shape = e.element ? elementRegistry.get(e.element.id) : e.shape
        if (this.showDialog && shape.type === 'bpmn:UserTask' && this.formDetail.length > 0) {
          this.formDetail.forEach(detail => {
            if (detail.id === e.element.id) {
              const g = svgCreate('g', {
                id: 'bpmn-dialog',
                class: 'bpmn-dialog'
              })
              // 提示框实现例子：https://market.m.taobao.com/app/fdilab/demoSvgToolKit/pages/svgToolKit/index.html
              const path = svgCreate('path', {
                d: 'M 0,0 L -15,15 H -95 Q -100,15 -100,20 V 110 Q -100,115 -95,115 H 95 Q 100,115 100,110 V 20 Q 100,15 95,15 H 15 z',
                class: 'bpmn-dialog'
              })
              const text = svgCreate('text', { x: 0, y: 0, 'font-size': '14', 'fill': '#8c8484' })
              detail.value.forEach(item => {
                const tspan = svgCreate('tspan', { x: this.dialogText.x, y: this.dialogText.y })
                tspan.innerHTML = item
                svgAppend(text, tspan)
                this.dialogText.y += 28
              })
              svgAppend(g, path)
              svgAppend(g, text)
              svgAppend(e.gfx, g)
            }
          })
        }
      })
      this.bpmnModeler.on('element.out', e => {
        const elementRegistry = this.bpmnModeler.get('elementRegistry')
        const shape = e.element ? elementRegistry.get(e.element.id) : e.shape
        if (shape.type === 'bpmn:UserTask') {
          for (let i = 0; i < e.gfx.children.length; i++) {
            if (e.gfx.children[i].id === 'bpmn-dialog') {
              svgRemove(e.gfx.children[i])
              this.dialogText.y = 80
            }
          }
        }
      })
    },
    openBpmn(file) {
      const reader = new FileReader()
      reader.readAsText(file, 'utf-8')
      reader.onload = () => {
        this.createNewDiagram(reader.result)
      }
    }
  }
}
</script>
<style lang="scss">
@import '../node_modules/bpmn-js/dist/assets/diagram-js.css';
@import '../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import '../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
@import '../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
@import '../node_modules/bpmn-js-bpmnlint/dist/assets/css/bpmn-js-bpmnlint.css';
@import './styles/app';

</style>
